package phaseutil

import (
	"context"
	"fmt"
	"regexp"
	"strconv"
	"strings"
	"sync"

	hashversion "github.com/hashicorp/go-version"
	"github.com/pkg/errors"
	confv1beta1 "gopkg.openfuyao.cn/bkecommon/cluster/api/v1beta1"
	bkenode "gopkg.openfuyao.cn/bkecommon/cluster/node"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"sigs.k8s.io/cluster-api/util/version"
	"sigs.k8s.io/controller-runtime/pkg/client"

	bkev1beta1 "gopkg.openfuyao.cn/cluster-api-provider-bke/api/v1beta1"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/annotation"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/clusterutil"
)

// DefaultRightVersionFields defines the default number of fields in a normalized version string.
const DefaultRightVersionFields = 4

// OnceFunc provides a sync.Once instance for one-time initialization.
var OnceFunc sync.Once

// NodeInfo returns a formatted string representation of a node (hostname/IP).
func NodeInfo(node confv1beta1.Node) string {
	if node.Hostname == "" {
		return node.IP
	}
	if node.IP == "" {
		return node.Hostname
	}
	return fmt.Sprintf("%s/%s", node.Hostname, node.IP)
}

// NodeRoleString returns a semicolon-separated string of node roles.
func NodeRoleString(node confv1beta1.Node) string {
	return strings.Join(node.Role, ";")
}

// IsMasterNode checks if the node has the master role.
func IsMasterNode(node *confv1beta1.Node) bool {
	for _, role := range node.Role {
		if role == bkenode.MasterNodeRole {
			return true
		}
	}
	return false
}

// IsWorkerNode checks if the node has the worker role.
func IsWorkerNode(node *confv1beta1.Node) bool {
	for _, role := range node.Role {
		if role == bkenode.WorkerNodeRole {
			return true
		}
	}
	return false
}

// IsEtcdNode checks if the node has the etcd role.
func IsEtcdNode(node *confv1beta1.Node) bool {
	for _, role := range node.Role {
		if role == bkenode.EtcdNodeRole {
			return true
		}
	}
	return false
}

// GetBKENodesFromNodesStatus extracts BKE nodes from NodesStates.
func GetBKENodesFromNodesStatus(nodesStatus confv1beta1.NodesStates) bkenode.Nodes {
	bkeNodes := bkenode.Nodes{}
	for _, nodeStatus := range nodesStatus {
		if nodeStatus.NeedSkip {
			continue
		}
		bkeNodes = append(bkeNodes, nodeStatus.Node)
	}
	return bkeNodes
}

// ConvertELBNodesToBKENodes converts ELB node IPs to BKE node objects.
func ConvertELBNodesToBKENodes(elbNodes []string, src bkenode.Nodes) bkenode.Nodes {
	var nodes bkenode.Nodes
	for _, ip := range elbNodes {
		for _, node := range src {
			if node.IP == ip {
				nodes = append(nodes, node)
			}
		}
	}
	return nodes
}

type NodeFilterOption func(*nodeFilterConfig)

func WithExcludeAppointmentNodes() NodeFilterOption {
	return func(c *nodeFilterConfig) {
		c.excludeAppointment = true
	}
}

type nodeFilterConfig struct {
	excludeAppointment bool
}

type NodePredicate func(ip string, nodeState *confv1beta1.NodeState) bool

// filterNodes 公共核心函数
func filterNodes(
	bkeCluster *bkev1beta1.BKECluster,
	predicate NodePredicate,
	opts ...NodeFilterOption,
) bkenode.Nodes {
	var cfg nodeFilterConfig
	for _, opt := range opts {
		opt(&cfg)
	}

	var nodes bkenode.Nodes
	for _, nodeState := range bkeCluster.Status.NodesStatus {
		if bkeCluster.GetNodeStateFlag(nodeState.Node.IP, bkev1beta1.NodeFailedFlag) {
			continue
		}
		if bkeCluster.GetNodeStateFlag(nodeState.Node.IP, bkev1beta1.NodeDeletingFlag) {
			continue
		}
		if nodeState.NeedSkip {
			continue
		}

		if predicate(nodeState.Node.IP, &nodeState) {
			nodes = append(nodes, nodeState.Node)
		}
	}

	if !cfg.excludeAppointment {
		return nodes
	}

	appointmentNodes := GetAppointmentAddNodes(bkeCluster)
	if appointmentNodes.Length() == 0 {
		return nodes
	}

	var finalNodes bkenode.Nodes
	for _, node := range nodes {
		if appointmentNodes.Filter(bkenode.FilterOptions{"IP": node.IP}).Length() == 0 {
			finalNodes = append(finalNodes, node)
		}
	}

	return finalNodes
}

// GetAgentPushedNodes returns nodes that have agents installed.
func GetNeedPushAgentNodes(bkeCluster *bkev1beta1.BKECluster) (nodes bkenode.Nodes) {
	return filterNodes(bkeCluster,
		func(ip string, ns *confv1beta1.NodeState) bool {
			return !bkeCluster.GetNodeStateFlag(ip, bkev1beta1.NodeAgentPushedFlag)
		},
		WithExcludeAppointmentNodes(),
	)
}

// GetAgentPushedNodes 获取已推送agent的节点
func GetAgentPushedNodes(bkeCluster *bkev1beta1.BKECluster) (nodes bkenode.Nodes) {
	for _, nodeStatus := range bkeCluster.Status.NodesStatus {
		if bkeCluster.GetNodeStateFlag(nodeStatus.Node.IP, bkev1beta1.NodeAgentPushedFlag) {
			nodes = append(nodes, nodeStatus.Node)
		}
	}
	return nodes
}

// GetNeedInitEnvNodes returns nodes that need environment initialization.
func GetNeedInitEnvNodes(bkeCluster *bkev1beta1.BKECluster) bkenode.Nodes {
	return filterNodes(bkeCluster,
		func(ip string, ns *confv1beta1.NodeState) bool {
			return !bkeCluster.GetNodeStateFlag(ip, bkev1beta1.NodeEnvFlag)
		},
		WithExcludeAppointmentNodes(),
	)
}

// ----------join--------------------

// GetNeedJoinNodes 获取需要加入集群的节点
func GetNeedJoinNodes(bkeCluster *bkev1beta1.BKECluster) bkenode.Nodes {
	return filterNodes(bkeCluster,
		func(ip string, ns *confv1beta1.NodeState) bool {
			return !bkeCluster.GetNodeStateFlag(ip, bkev1beta1.NodeBootFlag) &&
				!bkeCluster.GetNodeStateFlag(ip, bkev1beta1.MasterInitFlag)
		},
	)
}

// GetNeedJoinMasterNodes returns master nodes that need to join the cluster.
func GetNeedJoinMasterNodes(bkeCluster *bkev1beta1.BKECluster) (nodes bkenode.Nodes) {
	needAddNodes := GetNeedJoinNodes(bkeCluster).Master()
	if needAddNodes.Length() == 0 {
		return nodes
	}

	appointmentNodes := GetAppointmentAddNodes(bkeCluster)
	if appointmentNodes.Length() > 0 {
		return ComputeFinalAddNodes(needAddNodes, appointmentNodes)
	}

	return needAddNodes
}

// GetNeedJoinWorkerNodes returns worker nodes that need to join the cluster.
func GetNeedJoinWorkerNodes(bkeCluster *bkev1beta1.BKECluster) (nodes bkenode.Nodes) {
	needAddNodes := GetNeedJoinNodes(bkeCluster).Worker()
	if needAddNodes.Length() == 0 {
		return nodes
	}

	appointmentNodes := GetAppointmentAddNodes(bkeCluster)
	if appointmentNodes.Length() > 0 {
		return ComputeFinalAddNodes(needAddNodes, appointmentNodes)
	}

	return needAddNodes
}

// GetAppointmentAddNodes returns nodes scheduled for addition via annotation.
func GetAppointmentAddNodes(bkeCluster *bkev1beta1.BKECluster) (nodes bkenode.Nodes) {
	v, found := annotation.HasAnnotation(bkeCluster, annotation.AppointmentAddNodesAnnotationKey)
	if !found {
		return
	}
	nodesIP := strings.Split(v, ",")

	statusNodes := GetBKENodesFromNodesStatus(bkeCluster.Status.NodesStatus)
	for _, node := range statusNodes {
		if utils.ContainsString(nodesIP, node.IP) {
			nodes = append(nodes, node)
		}
	}
	return nodes
}

// ComputeFinalAddNodes computes the final set of nodes to be added.
func ComputeFinalAddNodes(needAddNodes bkenode.Nodes, appointmentNodes bkenode.Nodes) (nodes bkenode.Nodes) {
	for _, node := range needAddNodes {
		// Non-appointment nodes need immediate installation
		if appointmentNodes.Filter(bkenode.FilterOptions{"IP": node.IP}).Length() == 0 {
			nodes = append(nodes, node)
		}
	}
	return nodes
}

// ----------delete--------------------

// GetNeedDeleteNodes returns nodes that need to be deleted from the cluster.
func GetNeedDeleteNodes(bkeCluster *bkev1beta1.BKECluster) (nodes bkenode.Nodes) {
	statusNodes := GetBKENodesFromNodesStatus(bkeCluster.Status.NodesStatus)
	nodeTs, ok := bkenode.CompareBKEConfigNode(statusNodes, bkeCluster.Spec.ClusterConfig.Nodes)
	if !ok {
		return
	}
	for _, node := range nodeTs {
		if node.Operate == bkenode.RemoveNode {
			nodes = append(nodes, *node.Node)
		}
	}
	return
}

// GetNeedDeleteWorkerNodes returns worker nodes that need to be deleted.
func GetNeedDeleteWorkerNodes(bkeCluster *bkev1beta1.BKECluster) (nodes bkenode.Nodes) {
	appointmentNodes := GetAppointmentDeletedNodes(bkeCluster)
	if appointmentNodes.Length() == 0 {
		return nodes
	}
	needDepleteNodes := GetNeedDeleteNodes(bkeCluster).Worker()
	if needDepleteNodes.Length() == 0 {
		return nodes
	}
	return ComputeFinalDeleteNodes(needDepleteNodes, appointmentNodes)
}

// GetNeedDeleteMasterNodes returns master nodes that need to be deleted.
func GetNeedDeleteMasterNodes(bkeCluster *bkev1beta1.BKECluster) (nodes bkenode.Nodes) {
	appointmentNodes := GetAppointmentDeletedNodes(bkeCluster)
	if appointmentNodes.Length() == 0 {
		return nodes
	}
	needDepleteNodes := GetNeedDeleteNodes(bkeCluster).Master()
	if needDepleteNodes.Length() == 0 {
		return nodes
	}
	return ComputeFinalDeleteNodes(needDepleteNodes, appointmentNodes)
}

// GetAppointmentDeletedNodes returns nodes scheduled for deletion via annotation.
func GetAppointmentDeletedNodes(bkeCluster *bkev1beta1.BKECluster) (nodes bkenode.Nodes) {
	v, found := annotation.HasAnnotation(bkeCluster, annotation.AppointmentDeletedNodesAnnotationKey)
	if !found {
		return
	}
	nodesIP := strings.Split(v, ",")

	statusNodes := GetBKENodesFromNodesStatus(bkeCluster.Status.NodesStatus)
	for _, node := range statusNodes {
		if utils.ContainsString(nodesIP, node.IP) {
			nodes = append(nodes, node)
		}
	}
	return nodes
}

// RemoveAppointmentDeletedNodes removes a node from the appointment deletion list.
func RemoveAppointmentDeletedNodes(bkeCluster *bkev1beta1.BKECluster, nodeIP string) {
	v, found := annotation.HasAnnotation(bkeCluster, annotation.AppointmentDeletedNodesAnnotationKey)
	if !found {
		return
	}
	nodesIP := strings.Split(v, ",")
	for i, ip := range nodesIP {
		if ip == nodeIP {
			nodesIP = append(nodesIP[:i], nodesIP[i+1:]...)
			break
		}
	}
	annotation.SetAnnotation(bkeCluster, annotation.AppointmentDeletedNodesAnnotationKey, strings.Join(nodesIP, ","))
}

// ComputeFinalDeleteNodes computes the final set of nodes to be deleted.
func ComputeFinalDeleteNodes(needDepleteNodes bkenode.Nodes, appointmentNodes bkenode.Nodes) (nodes bkenode.Nodes) {
	for _, node := range needDepleteNodes {
		if appointmentNodes.Filter(bkenode.FilterOptions{"IP": node.IP}).Length() != 0 {
			nodes = append(nodes, node)
		}
	}
	return nodes
}

// ----------upgrade--------------------

// NormalizeVersion normalizes a version string to vX.Y.Z format, removing leading zeros.
func NormalizeVersion(version string) (string, error) {
	// Remove possible "v" prefix and convert to lowercase
	clean := strings.TrimPrefix(strings.ToLower(version), "v")

	// Use regex to extract number parts
	re := regexp.MustCompile(`(\d+)\.?(\d*)\.?(\d*)`)
	matches := re.FindStringSubmatch(clean)

	if len(matches) < DefaultRightVersionFields {
		return "", fmt.Errorf("invalid version format: %s", version)
	}

	parts := make([]string, DefaultRightVersionFields-1)
	for i := 1; i < DefaultRightVersionFields; i++ {
		part := matches[i]
		if part == "" {
			part = "0"
		}

		num, err := strconv.Atoi(part)
		if err != nil {
			return "", fmt.Errorf("invalid number in version: %s", part)
		}
		parts[i-1] = strconv.Itoa(num)
	}

	return "v" + strings.Join(parts, "."), nil
}

func NeedUpgrade(oldV, newV string) bool {
	oldVer, err := hashversion.NewVersion(oldV)
	if err != nil {
		return false
	}
	newVer, err := hashversion.NewVersion(newV)
	if err != nil {
		return false
	}
	return newVer.GreaterThan(oldVer)
}

func GetNeedUpgradeComponentNodes(bkeCluster *bkev1beta1.BKECluster) bkenode.Nodes {
	if bkeCluster.Status.OpenFuyaoVersion == "" {
		return nil
	}
	newConfig := bkeCluster.Spec.ClusterConfig
	if !NeedUpgrade(bkeCluster.Status.OpenFuyaoVersion, newConfig.Cluster.OpenFuyaoVersion) {
		return nil
	}
	var nodes bkenode.Nodes
	statusNodes := GetBKENodesFromNodesStatus(bkeCluster.Status.NodesStatus)
	for _, node := range statusNodes {
		if bkeCluster.GetNodeStateFlag(node.IP, bkev1beta1.NodeFailedFlag) {
			continue
		}
		nodes = append(nodes, node)
	}

	return nodes
}

// GetNeedUpgradeNodes returns nodes that need to be upgraded.
func GetNeedUpgradeNodes(bkeCluster *bkev1beta1.BKECluster) bkenode.Nodes {
	if bkeCluster.Status.OpenFuyaoVersion == "" {
		return nil
	}

	return compareVersionAndGetNodes(
		bkeCluster,
		bkeCluster.Status.OpenFuyaoVersion,
		bkeCluster.Spec.ClusterConfig.Cluster.OpenFuyaoVersion,
	)
}

// GetNeedUpgradeMasterNodes returns master nodes that need to be upgraded.
func GetNeedUpgradeMasterNodes(bkeCluster *bkev1beta1.BKECluster) (nodes bkenode.Nodes) {
	return GetNeedUpgradeNodes(bkeCluster).Master()
}

// GetNeedUpgradeWorkerNodes returns worker nodes that need to be upgraded.
func GetNeedUpgradeWorkerNodes(bkeCluster *bkev1beta1.BKECluster) (nodes bkenode.Nodes) {
	return GetNeedUpgradeNodes(bkeCluster).Worker()
}

// ----------loadbalance--------------------

// GetNeedLoadBalanceNodes returns nodes that need load balancing.
func GetNeedLoadBalanceNodes(bkeCluster *bkev1beta1.BKECluster) (nodes bkenode.Nodes) {

	needJoinMaster := GetNeedJoinMasterNodes(bkeCluster)
	needDeleteMaster := GetNeedDeleteMasterNodes(bkeCluster)

	// Get intersection of both
	nodesMap := make(map[string]confv1beta1.Node)
	for _, node := range needJoinMaster {
		nodesMap[node.IP] = node
	}
	for _, node := range needDeleteMaster {
		nodesMap[node.IP] = node
	}
	for _, node := range nodesMap {
		nodes = append(nodes, node)
	}
	return nodes

}

// ----------bke-config--------------------

// GetBKEConfigCMData retrieves BKE configuration data from ConfigMap.
func GetBKEConfigCMData(ctx context.Context, c client.Client) (map[string]string, error) {
	config := &corev1.ConfigMap{}
	err := c.Get(ctx, clusterutil.BKEConfigCmKey(), config)
	if err != nil {
		return nil, err
	}
	return config.Data, nil
}

// GetRemoteBKEConfigCM retrieves BKE configuration ConfigMap from remote cluster.
func GetRemoteBKEConfigCM(ctx context.Context, clientSet *kubernetes.Clientset) (*corev1.ConfigMap, error) {
	config, err := clientSet.CoreV1().ConfigMaps(clusterutil.BKEConfigCmKey().Namespace).Get(ctx, clusterutil.BKEConfigCmKey().Name, metav1.GetOptions{})
	if err != nil {
		if apierrors.IsNotFound(err) {
			return nil, nil
		}
		return nil, err
	}
	return config, nil
}

// MigrateBKEConfigCM migrates BKE configuration ConfigMap to remote cluster.
func MigrateBKEConfigCM(ctx context.Context, c client.Client, clientSet *kubernetes.Clientset) error {
	config := &corev1.ConfigMap{}
	err := c.Get(ctx, clusterutil.BKEConfigCmKey(), config)
	if err != nil {
		return err
	}
	remoteConfig := &corev1.ConfigMap{}
	remoteConfig.Name = clusterutil.BKEConfigCmKey().Name
	remoteConfig.Namespace = clusterutil.BKEConfigCmKey().Namespace
	remoteConfig.Data = config.Data

	// create ns
	ns := &corev1.Namespace{
		ObjectMeta: metav1.ObjectMeta{
			Name: remoteConfig.Namespace,
		},
	}

	_, err = clientSet.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{})
	if err != nil {
		if !apierrors.IsAlreadyExists(err) {
			return errors.Errorf("create remote bke config ns %q failed: %v", err, remoteConfig.Namespace)
		}
	}

	_, err = clientSet.CoreV1().ConfigMaps(remoteConfig.Namespace).Create(ctx, remoteConfig, metav1.CreateOptions{})
	if err != nil {
		if apierrors.IsAlreadyExists(err) {
			_, err = clientSet.CoreV1().ConfigMaps(remoteConfig.Namespace).Update(ctx, remoteConfig, metav1.UpdateOptions{})
			if err != nil {
				return errors.Errorf("update remote bke config cm failed: %v", err)
			}
			return nil
		}
		return errors.Errorf("create remote bke config cm failed: %v", err)
	}
	return nil
}

func MigratePatchConfigCM(ctx context.Context, c client.Client, clientSet *kubernetes.Clientset) error {
	patchNamespace := "openfuyao-patch"

	configMapList := &corev1.ConfigMapList{}
	if err := c.List(ctx, configMapList, &client.ListOptions{Namespace: patchNamespace}); err != nil {
		return errors.Wrapf(err, "failed to list ConfigMaps in namespace %q", patchNamespace)
	}

	ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: patchNamespace}}
	if _, err := clientSet.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}); err != nil {
		if !apierrors.IsAlreadyExists(err) {
			return errors.Wrapf(err, "create remote namespace %q failed", patchNamespace)
		}
	}

	for _, cm := range configMapList.Items {
		if !strings.HasPrefix(cm.Name, "cm.") {
			continue
		}

		cmCopy := &corev1.ConfigMap{
			ObjectMeta: metav1.ObjectMeta{
				Name:      cm.Name,
				Namespace: patchNamespace,
			},
			Data: cm.Data,
		}

		_, err := clientSet.CoreV1().ConfigMaps(patchNamespace).Create(ctx, cmCopy, metav1.CreateOptions{})
		if err == nil {
			continue
		}

		if !apierrors.IsAlreadyExists(err) {
			return errors.Wrapf(err, "create remote ConfigMap %s/%s failed", patchNamespace, cm.Name)
		}

		if _, updateErr := clientSet.CoreV1().ConfigMaps(patchNamespace).Update(ctx, cmCopy, metav1.UpdateOptions{}); updateErr != nil {
			return errors.Wrapf(updateErr, "update remote ConfigMap %s/%s failed", patchNamespace, cm.Name)
		}
	}

	return nil
}

// ----------bootstrap--------------------

// GetReadyBootstrapNodes returns nodes that are ready for bootstrap.
func GetReadyBootstrapNodes(bkeCluster *bkev1beta1.BKECluster) (nodes bkenode.Nodes) {
	statusNodes := GetBKENodesFromNodesStatus(bkeCluster.Status.NodesStatus)
	for _, node := range statusNodes {
		// agent is ready and env is ready and boot is not
		if bkeCluster.GetNodeStateFlag(node.IP, bkev1beta1.NodeAgentReadyFlag) &&
			bkeCluster.GetNodeStateFlag(node.IP, bkev1beta1.NodeEnvFlag) &&
			!bkeCluster.GetNodeStateFlag(node.IP, bkev1beta1.NodeBootFlag) {
			nodes = append(nodes, node)
		}
	}
	return nodes
}

// GetRemoteNodeByBKENode retrieves a Kubernetes node by BKE node information.
func GetRemoteNodeByBKENode(ctx context.Context, clientSet *kubernetes.Clientset, node confv1beta1.Node) (*corev1.Node, error) {
	return clientSet.CoreV1().Nodes().Get(ctx, node.Hostname, metav1.GetOptions{})
}

// GetClientURLByIP formats an etcd client URL from an IP address.
func GetClientURLByIP(ip string) string {
	return fmt.Sprintf("https://%s:2379", ip)
}

// GetNeedUpgradeEtcds returns etcds that need to be upgraded.
func GetNeedUpgradeEtcds(bkeCluster *bkev1beta1.BKECluster) bkenode.Nodes {
	return compareVersionAndGetNodes(
		bkeCluster,
		bkeCluster.Status.EtcdVersion,
		bkeCluster.Spec.ClusterConfig.Cluster.EtcdVersion,
	)
}

// compareVersionAndGetNodes is a helper function to compare versions and return nodes that need upgrading.
func compareVersionAndGetNodes(
	bkeCluster *bkev1beta1.BKECluster,
	oldVersionStr, newVersionStr string,
) (nodes bkenode.Nodes) {
	oldVersion, err := NormalizeVersion(oldVersionStr)
	if err != nil {
		return nil
	}
	oldv, err := version.ParseMajorMinorPatch(oldVersion)
	if err != nil {
		return nil
	}

	newVersion, err := NormalizeVersion(newVersionStr)
	if err != nil {
		return nil
	}
	newv, err := version.ParseMajorMinorPatch(newVersion)
	if err != nil {
		return nil
	}

	// compare cluster version upgrade
	switch version.Compare(newv, oldv) {
	case 1:
		statusNodes := GetBKENodesFromNodesStatus(bkeCluster.Status.NodesStatus)
		for _, node := range statusNodes {
			if bkeCluster.GetNodeStateFlag(node.IP, bkev1beta1.NodeFailedFlag) {
				continue
			}
			nodes = append(nodes, node)
		}
		return nodes
	default:
		// case -1 or 0: do nothing
		return nil
	}
}
